package cn.schoolwow.quickdao;

import cn.schoolwow.quickdao.builder.ddl.DDLBuilder;
import cn.schoolwow.quickdao.domain.Entity;
import cn.schoolwow.quickdao.domain.Property;
import cn.schoolwow.quickdao.domain.QuickDAOConfig;
import cn.schoolwow.quickdao.domain.util.MigrateOption;
import cn.schoolwow.quickdao.domain.util.SynchronizeTableStructureOption;
import com.alibaba.fastjson.JSONArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

/**
 * DAO工具类
 * */
public class DAOUtils {
    private static Logger logger = LoggerFactory.getLogger(DAOUtils.class);

    /**
     * 同步数据库结构
     * @param synchronizeTableStructureOption 同步表结构选项
     * */
    public static void synchronizeTableStructure(SynchronizeTableStructureOption synchronizeTableStructureOption){
        if(null==synchronizeTableStructureOption.source){
            throw new IllegalArgumentException("请指定迁移源数据库!");
        }
        if(null==synchronizeTableStructureOption.target){
            throw new IllegalArgumentException("请指定迁移目标数据库!");
        }
        List<Entity> sourceEntityList = new ArrayList<>();
        if(null==synchronizeTableStructureOption.tableNames||synchronizeTableStructureOption.tableNames.length==0){
            sourceEntityList = synchronizeTableStructureOption.source.getDbEntityList();
        }else{
            for(String tableName:synchronizeTableStructureOption.tableNames){
                Entity sourceEntity = synchronizeTableStructureOption.source.getDbEntity(tableName);
                if(null==sourceEntity){
                    logger.warn("数据库表不存在!表名:"+tableName);
                    continue;
                }
                sourceEntityList.add(sourceEntity);
            }
        }
        QuickDAOConfig quickDAOConfig = synchronizeTableStructureOption.target.getQuickDAOConfig();
        DDLBuilder ddlBuilder = quickDAOConfig.databaseProvider.getDDLBuilderInstance(quickDAOConfig);

        //记录需要新增的表,字段以及需要更新的字段
        List<Entity> addEntityList = new ArrayList<>();
        List<Property> addPropertyList = new ArrayList<>();
        Map<Property,Property> updatePropertyMap = new HashMap<>();
        for(Entity sourceEntity:sourceEntityList){
            Entity targetEntity = synchronizeTableStructureOption.target.getDbEntity(sourceEntity.tableName);
            if(null==targetEntity){
                addEntityList.add(sourceEntity);
                continue;
            }
            List<Property> sourcePropertyList = sourceEntity.properties;
            for(Property sourceProperty:sourcePropertyList){
                Property targetProperty = targetEntity.properties.stream().filter(property -> property.column.equalsIgnoreCase(sourceProperty.column)).findFirst().orElse(null);
                if(null==targetProperty){
                    addPropertyList.add(sourceProperty);
                }else if(synchronizeTableStructureOption.diffPropertyPredicate.test(sourceProperty,targetProperty)){
                    updatePropertyMap.put(sourceProperty,targetProperty);
                }
            }
        }

        StringBuilder builder = new StringBuilder();
        for(Entity entity:addEntityList){
            if(null!=synchronizeTableStructureOption.createTablePredicate&&!synchronizeTableStructureOption.createTablePredicate.test(entity)){
                continue;
            }
            builder.append(ddlBuilder.createTable(entity));
            if(synchronizeTableStructureOption.executeSQL){
                synchronizeTableStructureOption.target.create(entity);
            }
        }
        for(Property property:addPropertyList){
            if(null!=synchronizeTableStructureOption.createPropertyPredicate&&!synchronizeTableStructureOption.createPropertyPredicate.test(property)){
                continue;
            }
            builder.append(ddlBuilder.createProperty(property));
            if(synchronizeTableStructureOption.executeSQL){
                synchronizeTableStructureOption.target.createColumn(property.entity.tableName,property);
            }
        }
        Set<Map.Entry<Property,Property>> propertyEntrySet = updatePropertyMap.entrySet();
        for(Map.Entry<Property,Property> entry:propertyEntrySet){
            if(null!=synchronizeTableStructureOption.updatePropertyPredicate&&!synchronizeTableStructureOption.updatePropertyPredicate.test(entry.getKey(),entry.getValue())){
                continue;
            }
            builder.append(ddlBuilder.alterColumn(entry.getKey()));
            if(synchronizeTableStructureOption.executeSQL){
                synchronizeTableStructureOption.target.alterColumn(entry.getKey());
            }
        }
        synchronizeTableStructureOption.sql = builder.toString();
    }

    /**
     * 数据库迁移
     * @param migrateOption 迁移选项
     * */
    public static void migrate(MigrateOption migrateOption){
        if(null==migrateOption.source){
            throw new IllegalArgumentException("请指定迁移源数据库!");
        }
        if(null==migrateOption.target){
            throw new IllegalArgumentException("请指定迁移目标数据库!");
        }
        List<Entity> sourceEntityList = migrateOption.source.getDbEntityList();
        if(null!=migrateOption.tableFilter){
            sourceEntityList = sourceEntityList.stream().filter(migrateOption.tableFilter).collect(Collectors.toList());
        }
        if(null==sourceEntityList||sourceEntityList.isEmpty()){
            logger.warn("[数据库迁移]当前迁移源数据库表列表为空!");
            return;
        }
        //禁用外键约束
        migrateOption.target.enableForeignConstraintCheck(false);
        try{
            for(Entity sourceEntity:sourceEntityList){
                Entity targetEntity = sourceEntity.clone();
                if(null!=migrateOption.tableConsumer){
                    migrateOption.tableConsumer.accept(sourceEntity,targetEntity);
                }
                if(migrateOption.target.hasTable(targetEntity.tableName)){
                    logger.debug("[数据迁移]删除目标数据库表:{}",targetEntity.tableName);
                    migrateOption.target.dropTable(targetEntity.tableName);
                }
                logger.debug("[数据迁移]创建目标数据库表:{}",targetEntity.tableName);

                targetEntity.escapeTableName = migrateOption.target.getQuickDAOConfig().databaseProvider.escape(targetEntity.tableName);
                migrateOption.target.create(targetEntity);

                long count = migrateOption.source.query(sourceEntity.tableName).execute().count();
                int effect = 0;
                if(count>0){
                    //传输数据
                    long totalPage = count/migrateOption.batchCount+1;
                    logger.info("[数据迁移]准备迁移数据库表,源表:{},总记录数:{},迁移目标表:{}",sourceEntity.tableName,count,targetEntity.tableName);
                    for(int i=1;i<=totalPage;i++){
                        logger.debug("[数据迁移]准备传输第{}/{}页数据,源数据库表:{},目标数据库表:{}",i,totalPage,sourceEntity.tableName,targetEntity.tableName);
                        JSONArray array = migrateOption.source.query(sourceEntity.tableName)
                                .page(i,migrateOption.batchCount)
                                .execute()
                                .getArray();
                        effect += migrateOption.target.query(targetEntity.tableName)
                                .addInsert(array)
                                .execute()
                                .insertBatch();
                        logger.debug("[数据迁移]第{}/{}页数据传输完毕,迁移完成记录数:{}/{},源数据库表:{},目标数据库表:{}",i,totalPage,effect,count,sourceEntity.tableName,targetEntity.tableName);
                    }
                }
                logger.info("[数据迁移]表数据迁移完毕,迁移完成记录数:{}/{},源数据库表:{},目标数据库表:{}",effect,count,sourceEntity.tableName,targetEntity.tableName);
            }
        } finally {
            migrateOption.target.enableForeignConstraintCheck(true);
        }
    }
}
